package br.com.frbb.ugvbr.camera.opencv;

import java.util.List;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;

import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.Log;
import android.view.SurfaceHolder;

import br.com.frbb.ugvbr.camera.CameraCapturer;
import br.com.frbb.ugvbr.camera.CameraChannel;

public class OpenCVCameraCapturerImpl extends CameraCapturer implements Runnable {
	
	private static final String TAG = "br.com.frbb.ugvbr.camera.opencv.OpenCVCameraCapturerImpl";
	
	private VideoCapture camera;
	private int channel;
	Thread thread;
	
	public OpenCVCameraCapturerImpl(Activity activity) {
		super(activity);
		
		this.setScreenOrientation(activity.getRequestedOrientation());
		this.setPreviewChannel(CameraChannel.GRAYSCALE);
		
		thread = new Thread(this);
		
		if (!OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_2, activity, mOpenCVCallBack))
        {
        	Log.e(TAG, "Cannot connect to OpenCV Manager");
        }
	}
	
	public void setupCamera(int width, int height) {
        
    	// Log
    	Log.i(TAG, "setupCamera("+width+", "+height+")");
    	
        synchronized (this) {
            
        	// Verifica se a camera esta aberta
        	if (camera != null && camera.isOpened()) {
                
        		// Resgata lista de tamanhos suportados
            	List<Size> sizes = camera.getSupportedPreviewSizes();
                int mFrameWidth = width;
                int mFrameHeight = height;

                // Seleciona melhor tamanho
                {
                    double minDiff = Double.MAX_VALUE;
                    for (Size size : sizes) {
                        if (Math.abs(size.height - height) < minDiff) {
                            mFrameWidth = (int) size.width;
                            mFrameHeight = (int) size.height;
                            minDiff = Math.abs(size.height - height);
                        }
                    }
                }

                // Seta tamanho do preview
                camera.set(Highgui.CV_CAP_PROP_FRAME_WIDTH, mFrameWidth);
                camera.set(Highgui.CV_CAP_PROP_FRAME_HEIGHT, mFrameHeight);
                camera.set(Highgui.CV_CAP_PROP_ANDROID_FLASH_MODE, Highgui.CV_CAP_ANDROID_FLASH_MODE_TORCH);
                
                
                // Armazena internamente o tamanho do preview
                super.setupCamera(mFrameWidth, mFrameHeight);
            }
        }

    }

	public boolean openCamera() {
        Log.i(TAG, "Abrindo camera");
        
        synchronized (this) {
        	
        	releaseCamera();
	        
        	// Instancia camera
        	camera = new VideoCapture(Highgui.CV_CAP_ANDROID);
        	
        	// Verifica se a camera foi aberta com sucesso
	        if (!camera.isOpened()) {

	        	// Libera a camera
	        	camera.release();
	            camera = null;
	            
	            // Log de erro ao abrir camera
	            Log.e(TAG, "Failed to open native camera");
	            
	            return false;
	        }
	    }
        
        return true;
    }
    
    public void releaseCamera() {
        Log.i(TAG, "releaseCamera");
        
        synchronized (this) {
	        
        	if (camera != null) {
        		camera.release();
        		camera = null;
            }
        }
    }
	
    public void setPreviewChannel(CameraChannel cameraChannel) {
		if(cameraChannel == CameraChannel.GRAYSCALE) {
			this.channel = Highgui.CV_CAP_ANDROID_GREY_FRAME;
		}
		else if (cameraChannel == CameraChannel.RGB) {
			this.channel = Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGB;
		}
	}

	public void run() {
		Log.i(TAG, "Iniciando thread de captura.");
		
        while (true) {
            Bitmap bmp = null;
            Mat mat = new Mat();
            Mat flipped = new Mat();
            
            Log.i(TAG, "Iniciando Loop");
            
            if (camera == null)
                break;

            if (!camera.grab()) {
                Log.e(TAG, "mCamera.grab() failed");
                break;
            }
            
            camera.retrieve(mat, this.channel);
            
            if(this.screenOrientation == 1) {
            	Core.transpose(mat, flipped);
            	Core.flip(flipped, mat, 1);
            }
            
            if(previewCallback == null) {
	            bmp = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888);
	            Utils.matToBitmap(mat, bmp);
            }
            else {
            	synchronized(previewCallback) {
            		bmp = previewCallback.onPreview(mat);
            	}
            }
                
            if (bmp != null) {
                if(surfaceHolder != null) {
	                synchronized (surfaceHolder) {
	                	if(!surfaceHolder.isCreating()) {
		            		Canvas canvas = surfaceHolder.lockCanvas();
		                    if (canvas != null) {
		                    	canvas.drawBitmap(bmp, (canvas.getWidth() - bmp.getWidth()) / 2, (canvas.getHeight() - bmp.getHeight()) / 2, null);
		                        surfaceHolder.unlockCanvasAndPost(canvas);
		                    }
	                	}
					}
                }
                
                bmp.recycle();
            }
        }

        Log.i(TAG, "Thread de captura encerrada.");
	}

	private BaseLoaderCallback  mOpenCVCallBack = new BaseLoaderCallback(activity) {
    	@Override
    	public void onManagerConnected(int status) {
    		switch (status) {
				case LoaderCallbackInterface.SUCCESS:
				{
					openCamera();
					
					if(width == 0) {
						setupCamera(240, 160);
					}
					
					thread.start();
				} break;
				default:
				{
					super.onManagerConnected(status);
				} break;
			}
    	}
	};
}
